package com.brightworks.scripthelper.analyzers
{
    import com.brightworks.scripthelper.constants.LineTypeConstants;
    import com.brightworks.scripthelper.constants.MiscConstants;
    import com.brightworks.scripthelper.constants.PunctuationConstants;
    import com.brightworks.util.Log;
    import com.brightworks.util.Utils_String;

    import flash.utils.Dictionary;

    public class ChunkAnalyzer
    {
        public var characterCount:int;
        public var chunkNumber:int;
        public var lineAnalyzerListIncludingCommentLines:Array;
        public var lineCode:String;
        public var roleName:String;

        private var _lineInfoList:Dictionary; // Properties are chunk line type ID constants (defined in LineTypeConstants), values are LineTypeAnalyzer instances

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        //
        //          Getters & Setters
        //
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  

        public function get lineCodeCharCount():int
        {
            if (!(lineCode is String))
                Log.fatal("ChunkAnalyzer.lineCodeCharCount(): lineCode prop isn't set to a string");
            return lineCode.length;
        }

        public function get lineCodeSyllableCount():int
        {
            if (!(lineCode is String))
                Log.fatal("ChunkAnalyzer.lineCodeSyllableCount(): lineCode prop isn't set to a string");
            var result:int = lineCode.length;
            result += Utils_String.getCountOfCharsInString(["0", "7"], lineCode);
            result += 2 * Utils_String.getCountOfCharsInString(["w", "W"], lineCode);
            return result;
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        //
        //          Public Methods
        //
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

        public function ChunkAnalyzer()
        {
            _lineInfoList = new Dictionary();
        }

        public function addLineTypeAnalyzer(analyzer:LineTypeAnalyzer):void
        {
            if (_lineInfoList.hasOwnProperty(analyzer.lineTypeId))
                Log.fatal("ChunkAnalyzer.addLineTypeAnalyzer(): Line analyzer of type '" + analyzer.lineTypeId + "' has already been added to this chunk");
            _lineInfoList[analyzer.lineTypeId] = analyzer;
        }

        public function getLineTypeAnalyzer(lineType:String):LineTypeAnalyzer
        {
            if (!_lineInfoList)
                throw new Error("ChunkAnalyzer.getLineTypeAnalyzer(): _lineInfoList is null");
            if (!_lineInfoList.hasOwnProperty(lineType))
                throw new Error("ChunkAnalyzer.getLineTypeAnalyzer(): _lineInfoList has no " + lineType + " property");
            var result:LineTypeAnalyzer = _lineInfoList[lineType];
            return result;
        }

        public function getLineNumber(lineType:String):int
        {
            var la:LineTypeAnalyzer = getLineTypeAnalyzer(lineType);
            var result:int = la.lineNumber;
            return result;
        }

        public function getLineText(lineType:String):String
        {
            var la:LineTypeAnalyzer = getLineTypeAnalyzer(lineType);
            var result:String = la.lineText;
            return result;
        }

        public function getLineTextCharCount(lineType:String):int
        {
            var result:int = getLineText(lineType).length;
            return result;
        }

        public function getLineTextWordCount(lineType:String):int
        {
            if (lineType != LineTypeConstants.LINE_TYPE_ID__CHUNK__NATIVE_LANGUAGE)
                Log.fatal("ChunkAnalyzer.getLineTextWordCount(): Method is currently only implemented for LINE_TYPE_ID__CHUNK__NATIVE_LANGUAGE, i.e., at present, English.");
            var result:int = Utils_String.getWordCountForString(getLineText(lineType));
            return result;
        }

        public function getLineTypeName(lineType:String):String
        {
            var la:LineTypeAnalyzer = getLineTypeAnalyzer(lineType);
            var result:String = la.lineTypeId;
            return result;
        }

        public function getProblemReports():Array
        {
            var problemList:Array = [];
            checkForPunctuationMismatchesBetweenHanziAndPinyinLines(problemList);
            checkEllipses(problemList);
            return problemList;
        }

        public function isLineProcededByIgnoreProblemsCommentLine(lineType:String):Boolean
        {
            var indexOfLine:int = getIndexOfLineInLineAnalyzerListIncludingCommentLines(lineType);
            if (indexOfLine == 0)
                return false;
            var precedingAnalyzer:LineTypeAnalyzer = lineAnalyzerListIncludingCommentLines[indexOfLine - 1];
            var result:Boolean = false;
            if (precedingAnalyzer.lineTypeId == LineTypeConstants.LINE_TYPE_ID__COMMENT)
            {
                if (LineTypeAnalyzer_Comment(precedingAnalyzer).isIgnoreProblemsComment())
                    result = true;
            }
            return result;
        }

        public function isPinyinLineFinalOrOnlyChunkInSentence():Boolean
        {
            var pinyinLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
            var result:Boolean = false;
            if (!Utils_String.doesStringEndWithString(pinyinLineText, "..."))
            {
                if (Utils_String.doesStringEndWithString(pinyinLineText, [".", "?", "!"]))
                    result = true;
            }
            return result;
        }

        public function isPinyinLineFirstOrOnlyChunkInSentence():Boolean
        {
            var pinyinLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
            var firstNonQuoteChar:String = Utils_String.getFirstCharInStringThatIsNotOtherSpecifiedChars(pinyinLineText, PunctuationConstants.QUOTATION_MARKS__DOUBLE);
            var result:Boolean = Utils_String.isCharUpperCase(firstNonQuoteChar);
            return result;
        }

        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        //
        //          Private Methods
        //
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

        /**
         *
         * @param problemList
         *
         */
        private function checkEllipses(problemList:Array):void
        {
            var bIgnoreHanziLine:Boolean = isLineProcededByIgnoreProblemsCommentLine(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
            var bIgnorePinyinLine:Boolean = isLineProcededByIgnoreProblemsCommentLine(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
            if (bIgnoreHanziLine && bIgnorePinyinLine)
                return;
            var hanziLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
            var pinyinLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
            var bHanziLineEllipsis:Boolean = Utils_String.doesStringEndWithString(hanziLineText, "……");
            var bPinyinLineEllipsis:Boolean = Utils_String.doesStringEndWithString(pinyinLineText, "...");
            if ((bHanziLineEllipsis && !bPinyinLineEllipsis) || (!bHanziLineEllipsis && bPinyinLineEllipsis))
            {
                var hanziLineNumber:int = getLineNumber(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
                var pinyinLineNumber:int = getLineNumber(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
                var problemString:String = "";
                problemString += "Pinyin and hanzi lines in the same chunk should both have an ellipsis if either has an ellipsis. Only one of these lines has an ellipsis:\n";
                problemString += "   Line " + pinyinLineNumber + ":  " + pinyinLineText + "\n";
                problemString += "   Line " + hanziLineNumber + ":  " + hanziLineText + "\n";
                problemList.push(problemString);
            }
        }

        private function checkForPunctuationMismatchesBetweenHanziAndPinyinLines(problemList:Array):void
        {
            var bIgnoreHanziLine:Boolean = isLineProcededByIgnoreProblemsCommentLine(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
            if (bIgnoreHanziLine)
                return;
            var hanziLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
            var pinyinLineText:String = getLineText(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
            var hanziPunctuationString:String = extractHanziLinePunctuationMarks(hanziLineText);
            var pinyinPunctuationString:String = extractPinyinLinePunctuationMarks(pinyinLineText);
            var bProblem:Boolean = false;
            var hanziChar:String;
            var pinyinChar:String;
            if (hanziPunctuationString.length != pinyinPunctuationString.length)
            {
                bProblem = true;
            }
            else
            {
                var charCount:int = hanziPunctuationString.length;
                for (var charIndex:int = 0; charIndex < charCount; charIndex++)
                {
                    hanziChar = hanziPunctuationString.charAt(charIndex);
                    pinyinChar = pinyinPunctuationString.charAt(charIndex);
                    if (!doesHanziPunctuationMarkMatchPinyinPunctuationMark(hanziChar, pinyinChar))
                    {
                        bProblem = true;
                        break;
                    }
                }
            }
            if (bProblem)
            {
                var hanziLineNumber:int = getLineNumber(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE);
                var pinyinLineNumber:int = getLineNumber(LineTypeConstants.LINE_TYPE_ID__CHUNK__TARGET_LANGUAGE__ROMANIZED);
                var combinedPunctuationStringForHanzi:String = extractCombinedPunctuationStringFromLine(hanziLineText);
                var combinedPunctuationStringForPinyin:String = extractCombinedPunctuationStringFromLine(pinyinLineText);
                var problemString:String = "";
                problemString += "Pinyin and hanzi lines in the same chunk should have 'corresponding' punctuation. In specific, this means:\n";
                problemString += MiscConstants.CHINESE_PUNCTUATION_RULES + "\n";
                problemString += "Here are two lines with 'non-corresponding' punctuation:\n";
                problemString += "   Line " + pinyinLineNumber + ":  " + pinyinLineText + "\n";
                problemString += "   Line " + hanziLineNumber + ":  " + hanziLineText + "\n";
                problemString += "Here is the puctuation that I can see for these lines:\n";
                problemString += "   Line " + pinyinLineNumber + ":  " + combinedPunctuationStringForPinyin + "\n";
                problemString += "   Line " + hanziLineNumber + ":  " + combinedPunctuationStringForHanzi + "\n";
                problemList.push(problemString);
            }
        }

        private function doesHanziPunctuationMarkMatchPinyinPunctuationMark(hanziMark:String, pinyinMark:String):Boolean
        {
            return doesPunctuationMarkMatchPunctuationMark(hanziMark, pinyinMark, (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.hanziPunctuation as Array), (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.pinyinPunctuation as Array));
        }

        private function doesPinyinPunctuationMarkMatchHanziPunctuationMark(pinyinMark:String, hanziMark:String):Boolean
        {
            return doesPunctuationMarkMatchPunctuationMark(pinyinMark, hanziMark, (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.pinyinPunctuation as Array), (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.hanziPunctuation as Array));
        }

        private function doesPunctuationMarkMatchPunctuationMark(markToMatch:String, possibleMatch:String, markToMatchList:Array, possibleMatchList:Array):Boolean
        {
            // markToMatchList is an array containing the markToMatch. We use it to get 
            // the position of the mark in the list. The possibleMatchList contains
            // marks that are the cross-language equivilent of the marks in the
            // markToMatchList, so once we get said position, we see if the char
            // in the same position in the possibleMatchList is the same as the 
            // passed possibleMatch char.
            //
            // But it's actually more complex than that. Both lists may contain arrays
            // as well as strings. So we also need to see if the markToMatch char is in
            // an array within the markToMatchList array. If so, we use the position of 
            // that subarray. And when we look at the contents that are at that position
            // in the possibleMatchList array, if it's a string we just see if it matches,
            // if it's an array we see if one of its items matches.
            //
            // These comments are written very quickly, but are hopefully better than nothing  :)
            if (markToMatch.length != 1)
                Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): markToMatch must be a one-char string");
            if (possibleMatch.length != 1)
                Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): possibleMatch must be a one-char string");
            if (markToMatchList.length != possibleMatchList.length)
                Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): markToMatchList and possibleMatchList aren't the same length");
            var indexOfMarkToMatchInList:int = -1;
            var currIndex:int = -1;
            var subArray:Array;
            var subArrayItem:String;
            var bIndexFound:Boolean = false;
            for each (var markToMatchListItem:Object in markToMatchList)
            {
                if (bIndexFound)
                    break;
                currIndex ++;
                if (markToMatchListItem is String)
                {
                    if (String(markToMatchListItem).length != 1)
                        Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): all string items in markToMatchList must be one-char strings");
                    if (markToMatchListItem == markToMatch)
                    {
                        indexOfMarkToMatchInList = currIndex;
                        break;
                    }
                }
                else if (markToMatchListItem is Array)
                {
                    subArray = (markToMatchListItem as Array);
                    for each (subArrayItem in subArray)
                    {
                        if (subArrayItem.length != 1)
                            Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): all items in markToMatchList sub-arrays must be strings");
                        if (subArrayItem == markToMatch)
                        {
                            indexOfMarkToMatchInList = currIndex;
                            bIndexFound = true;
                            break;
                        }
                    }
                }
                else
                {
                    Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): item in markToMatchList is neither string nor array");
                }
            }
            if (indexOfMarkToMatchInList == -1)
                Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): can't find markToMatch in markToMatchList");
            var result:Boolean = false;
            var possibleMatchItem:Object = possibleMatchList[indexOfMarkToMatchInList];
            if (possibleMatchItem is String)
            {
                if (possibleMatchItem == possibleMatch)
                    result = true;
            }
            else if (possibleMatchItem is Array)
            {
                subArray = (possibleMatchItem as Array);
                for each (subArrayItem in subArray)
                {
                    if (subArrayItem == possibleMatch)
                    {
                        result = true;
                        break;
                    }
                }
            }
            else
            {
                Log.fatal("ChunkAnalyzer.doesPunctuationMarkMatchPunctuationMark(): item in possibleMatchList is neither string nor array");
            }
            return result
        }

        private function extractCharsContainedInPuctuationMarkListFromString(s:String, punctuationMarkList:Array):String
        {
            var result:String = "";
            var charCount:int = s.length;
            var currCharFromString:String;
            for (var charIndex:int = 0; charIndex < charCount; charIndex++)
            {
                currCharFromString = s.charAt(charIndex);
                for each (var punctuationMarkListItem:Object in punctuationMarkList)
                {
                    if (punctuationMarkListItem is String)
                    {
                        if (punctuationMarkListItem == currCharFromString)
                            result += currCharFromString;
                    }
                    else if (punctuationMarkListItem is Array)
                    {
                        var subArray:Array = (punctuationMarkListItem as Array);
                        for each (var subArrayItem:String in subArray)
                        {
                            if (subArrayItem == currCharFromString)
                            {
                                result += currCharFromString;
                                break;
                            }
                        }
                    }
                    else
                    {
                        Log.fatal("ChunkAnalyzer.extractCharsContainedInPuctuationMarkListFromString(): item in punctuationMarkList is neither string nor array");
                    }
                }
            }
            return result;
        }

        private function extractCombinedPunctuationStringFromLine(s:String):String
        {
            s = Utils_String.removeSubstringFromString("\\.{3}", s);
            return extractCharsContainedInPuctuationMarkListFromString(s, (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.combinedPunctuation as Array));
        }

        private function extractHanziLinePunctuationMarks(s:String):String
        {
            return extractCharsContainedInPuctuationMarkListFromString(s, (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.hanziPunctuation as Array));
        }

        private function extractPinyinLinePunctuationMarks(s:String):String
        {
            s = Utils_String.removeSubstringFromString("\\.{3}", s);
            return extractCharsContainedInPuctuationMarkListFromString(s, (PunctuationConstants.HANZI_TO_PINYIN_PUNCTUATION_MATCHING_INFO.pinyinPunctuation as Array));
        }

        private function getIndexOfLineInLineAnalyzerListIncludingCommentLines(lineType:String):int
        {
            var result:int = -1;
            for (var i:int = 0; i < lineAnalyzerListIncludingCommentLines.length; i++)
            {
                var analyzer:LineTypeAnalyzer = lineAnalyzerListIncludingCommentLines[i];
                if (analyzer.lineTypeId == lineType)
                {
                    result = i;
                    break;
                }
            }
            if (result == -1)
                Log.fatal("ChunkAnalyzer.getIndexOfLineInLineAnalyzerListIncludingCommentLines(): no line analyzer of that type is in list");
            return result;
        }

    }
}








